/*
 * sock test
 *
 * test for lib/sock.c
 *
 * $Id: test_sock.c 29 2009-05-05 16:31:09Z ybc2084 $
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <semaphore.h>

#include "param.h"
#include "sock.h"

#define ERRHEAD __FILE__ ":"

static char *tmplog	 = "./test_sock.log";
static char *semname = __FILE__ ".sem";

void except(const char * msg)
{
	perror(msg);
}

static int dupTmplog2Stdout(void)
{
	int		tmpfd;
	int		rc;

	/* duplicate stdout to tmplog */
	tmpfd = open(tmplog, O_WRONLY);
	assert(tmpfd > 0);

	close(STDOUT_FILENO);
	rc = dup2(tmpfd, STDOUT_FILENO);
	assert(rc > 0 && rc == STDOUT_FILENO);

	return (rc);
}

static int startupServer(const char * const addr, int port)
{
	char   *netcat = "nc";
	char	porstr[16];
	sem_t  *psem;
	int		rc;

	dupTmplog2Stdout();

	/* set port # */
	memset(porstr, 0, 16);
	sprintf(porstr, "%d", port);
	char *argv[] = {
		netcat, "-l", "-n", (char*)addr, porstr, (char *)NULL
	};

	/* open and post sema in child */
	psem = sem_open(semname, 0);
	assert(psem != SEM_FAILED);

	rc = sem_post(psem);
	if (rc < 0) {
		except("post sema fail");
		goto outlet;
	}

	execvp(netcat, argv);

outlet:
	exit(0);
}

/**********************************************************
 * normal case
 *
 * startup a nc as server, nc's output is redirected to a
 * temp file. proper addr:port is provided to connTo(), 
 * connTo() should return a valid socket, data is then 
 * transmited on the returned socket, if no errno occur, 
 * nc will receive that data and dump to the temp file,
 * check is executed by comparing the original data and
 * data in the temp file.
 **********************************************************/
static int test_connTo_normal(void)
{
	pid_t	ncpid;
	int		ncstatus;
	int		rc;
	sem_t  *psem;
	int		tmpfd;
	int		sock;
	int		n;
	char	xmitMesg[100];
	char	rcvdMesg[100];
	char   *p;

	/* create tmp file */
	tmpfd = open(tmplog, O_WRONLY | O_CREAT | O_TRUNC);
	assert(tmpfd);
	close(tmpfd);

	/* create semaphore */
	psem = sem_open(semname, O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, 0);
	if (psem == SEM_FAILED) {
		except("open sema failed");
		goto outlet_1;
	}

	if ((ncpid = fork()) == 0) {
		/* child */

		startupServer(LOOPBACKIP, DECCOMMPORT);

	} else {
		/* parent */

		/* waiting to child to run */
		rc = sem_wait(psem);
		if (rc < 0) {
			except("wait sema fail");
			goto outlet_1;
		}

		/* 
		 * though child process has post a semaphore to indicate
		 * that it's running, but since the system scheduler is
		 * unpredicated, so we'd best wait a short glance here
		 */
		usleep(200);		

		do {
			sock = connTo(LOOPBACKIP, DECCOMMPORT);
		} while (sock < 0 && 
					(errno == EAGAIN || 
					 errno == EINTR || 
					 errno == ECONNREFUSED));

		usleep(100);		/* waiting a short glance */

		/*
		 * try to send some mesg to peer
		 */
		memset(xmitMesg, 0, 100);
		strncpy(xmitMesg, "hello rockins\n", 100);
		n = write(sock, xmitMesg, strlen(xmitMesg));
		if (n != strlen(xmitMesg)) {
			except("write to sock fail");
			goto outlet_2;
		}

		usleep(100);		/* waiting a short glance to */

		/*
		 * verify the mesg peer end received
		 */
		tmpfd = open(tmplog, O_RDONLY);
		assert(tmpfd);
		memset(rcvdMesg, 0, 100);
		p = &rcvdMesg[0];
		while ((n = read(tmpfd, p, rcvdMesg + 100 - p))) {
			if (n < 0) {
				except("read log file fail");
				goto outlet_3;
			}
			p += n;
		}

		/*printf("xmitMesg:\t%s", xmitMesg);
		printf("rcvdMesg:\t%s", rcvdMesg);*/
		rc = strncmp(xmitMesg, rcvdMesg, 100);
		assert(rc == 0);

outlet_3:
		close(tmpfd);
		unlink(tmplog);

outlet_2:
		/*shutdown(mp3dec.commSock, SHUT_WR);*/
		close(sock);

outlet_1:
		/* release sema */
		sem_close(psem);
		sem_unlink(semname);

		rc = kill(ncpid, SIGKILL);
		assert(rc == 0);

		waitpid(ncpid, &ncstatus, 0);	/* reclaim */
	}

	return (0);
}

/**********************************************************
 * abnormal case: bad address
 *
 * supply variant bad address (invalid, cannot reach) to
 * connTo(). for all cases, the returned value should be
 * -1 whenever.
 **********************************************************/
static int test_connTo_badaddr(void)
{
	int		sock;

	/*
	 * ip address - NULL
	 */
	sock = connTo(NULL, DECCOMMPORT);
	assert(sock == -1);

	/*
	 * invalid ip address
	 */
	sock = connTo("127.0.1", DECCOMMPORT);
	assert(sock == -1);

	/*
	 * destination cannot reach, this may need 
	 * a lot of time to complete. if you need to
	 * check this case, kindly uncomment it.
	 */
	/*sock = connTo("10.0.0.1", DECCOMMPORT);
	assert(sock == -1);*/

	/*
	 * bad address types (should be string)
	 * Note: following code shouldn't be compiled through,
	 * to enable this snippet, replace 0 with 1.
	 */
#if 0
	sock = connTo(1, DECCOMMPORT);
	assert(sock == -1);

	sock = connTo(1.0, DECCOMMPORT);
	assert(sock == -1);

	sock = connTo('a', DECCOMMPORT);
	assert(sock == -1);
#endif

	return (0);
}

/**********************************************************
 * abnormal case: bad port
 *
 * provide variant bad port (invalid, out range) to connTo(),
 * for all cases, connTo() should return -1.
 **********************************************************/
static int test_connTo_badport(void)
{
	int		sock;

	/*
	 * port is zero
	 */
	sock = connTo(LOOPBACKIP, 0);
	assert(sock == -1);

	/*
	 * port is negtive
	 */
	sock = connTo(LOOPBACKIP, -2);
	assert(sock == -1);

	/*
	 * port beyond the upper range of legimate port #
	 */
	sock = connTo(LOOPBACKIP, 65537);
	assert(sock == -1);

	/*
	 * bad types for port.
	 * theoretically, float and char type will be casted
	 * to int type automatically, so, if there's some
	 * process listening on port 10 or 98 (value of 'b'),
	 * results of following tests should be false.
	 */
	sock = connTo(LOOPBACKIP, 10.0);
	assert(sock == -1);

	sock = connTo(LOOPBACKIP, 'b');
	assert(sock == -1);

	/*
	 * Note: following code shouldn't be compiled through,
	 * to enable this check, replace 0 in the condition
	 * compilation with 1.
	 */
#if 0
	sock = connTo(LOOPBACKIP, "abcdef");
	assert(sock == -1);

	sock = connTo(LOOPBACKIP, NULL);
	assert(sock == -1);

	char	carr[10] = {'h', 'i', 't', 'o', 'm', 0};
	sock = connTo(LOOPBACKIP, carr);
	assert(sock == -1);
#endif

	return (0);
}

/*
 * wait child 'pid' terminate
 */
static int waitchild(pid_t pid)
{
    pid_t w;
    int status;
    while (1) {

        kill(pid, SIGKILL);
        w = waitpid(pid, &status, 0);	/* reclaim */
        if ((w == pid && (WIFSIGNALED(status) || WIFEXITED(status))) ||
                (w == -1 && errno == ECHILD)) {
            break;
        } else {
            perror("waitpid");
            return (-1);
        }
    }

    return (0);
}

/********************************************************************
 * run listenOn() on 'LOOPBACKIP:DECOMMPORT', if it succeed, then 
 * client socket which try to connect to it will succeed, too. return
 * 0 indicate success, else return -1.
 ********************************************************************/
static int test_listenOn_normal(void)
{
    pid_t   pid;
    int     cliSock;
    int     rc;
    sem_t  *psem;

    /* create semaphore */
    psem = sem_open(semname, O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, 0);
    if (psem == SEM_FAILED) {
        except("open sema failed");
        rc = -1;
        goto outlet;
    }

    if ((pid = fork()) == -1) {

        /* error */

        perror("fork");
        rc = -1;
        goto outlet;
    } else if (pid == 0) {

        /* child */

        int svrSock = listenOn(LOOPBACKIP, DECCOMMPORT);
        if (svrSock < 0)
            return (-1);

        /* open and post sema in child */
        psem = sem_open(semname, 0);
        assert(psem != SEM_FAILED);

        rc = sem_post(psem);
        if (rc < 0) {
            except("post sema fail");
            exit(-1);
        }

        sem_close(psem);

        for (;;) {
            do {
                cliSock = accept(svrSock, NULL, NULL);
            } while (cliSock == -1 && errno == EINTR);
        }

        exit(0);
    } 

    /* parent */

    /* waiting to child to run */
    rc = sem_wait(psem);
    if (rc < 0) {
        except("wait sema fail");
        rc = 1;
        goto outlet2;
    }

    /* 
     * though child process has post a semaphore to indicate
     * that it's running, but since the system scheduler is
     * unpredicated, so we'd best wait a short glance here
     */
    usleep(200000);		

    do {
        cliSock = connTo(LOOPBACKIP, DECCOMMPORT);
    } while (cliSock < 0 && 
            (errno == EAGAIN || 
             errno == EINTR || 
             errno == ECONNREFUSED));

    if (cliSock < 0)
        rc = -1;

outlet2:

    /* release sema */
    sem_close(psem);
    sem_unlink(semname);

    waitchild(pid);

outlet:
    return (rc);
}

/********************************************************************
 * listenOn() bad address. all these cases should return -1, if it
 * isn't, return -1 to indicate failness of test.
 ********************************************************************/
static int test_listenOn_badaddr(void)
{
    int svrSock;
    
    svrSock = listenOn(NULL, DECCOMMPORT);
    assert(svrSock == -1);

    svrSock = listenOn("127.0.1", DECCOMMPORT);
    assert(svrSock == -1);

    return (0);
}

/**********************************************************
* listenOn() bad port (invalid, out range),  
* for all these cases, listenOn() should return -1.
**********************************************************/
static int test_listenOn_badport(void)
{
   int		svrSock;

   /*
    * port is zero
    */
   svrSock = listenOn(LOOPBACKIP, 0);
   assert(svrSock == -1);

   /*
    * port is negtive
    */
   svrSock = listenOn(LOOPBACKIP, -2);
   assert(svrSock == -1);

   /*
    * port beyond the upper range of legimate port #
    */
   svrSock = listenOn(LOOPBACKIP, 65537);
   assert(svrSock == -1);

   return (0);
}

/*
 * connTo() test cases.
 * return -1 to indicate failness.
 */
static int test_connTo(void)
{
    int	rc;

    /* test connTo() */

	rc = test_connTo_normal();
	if (rc < 0)	goto testnotpass;

	rc = test_connTo_badaddr();
	if (rc < 0)	goto testnotpass;

	rc = test_connTo_badport();
	if (rc < 0)	goto testnotpass;

    return (0);

testnotpass:
    return (-1);
}

/*
 * listenOn() test cases.
 * return -1 to indicate failness.
 */
static int test_listenOn(void)
{
    int rc;
    
    /* test listenOn() */

    rc = test_listenOn_normal();
	if (rc < 0)	goto testnotpass;

    printf("test_listenOn_normal()\n");

    rc = test_listenOn_badaddr();
	if (rc < 0)	goto testnotpass;

    printf("test_listenOn_badaddr() passed\n");

    rc = test_listenOn_badport();
	if (rc < 0)	goto testnotpass;

    printf("test_listenOn_badport() passed\n");

    return (0);

testnotpass:
    return (-1);
}

int main(void)
{
	int	rc;

    /* test connTo() */

	rc = test_connTo();
	if (rc < 0)	goto testnotpass;

    printf("test_connTo() passed\n");

    /* test listenOn() */

    rc = test_listenOn();
	if (rc < 0)	goto testnotpass;

    printf("test_listenOn() passed\n");

	printf("OK:%s:all test cases pass\n", __FILE__);

	return (0);

testnotpass:
	printf("BAD:%s:test cases do not pass\n", __FILE__);
	return (-1);
}

